﻿using System.Security.Cryptography;

namespace SharpCrack.HashAlgorithms
{
	/// <summary>
	/// Computes the CRC32 value for the input data.
	/// </summary>
	[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "CRC")]
	public sealed class CRC32 : HashAlgorithm
	{
		public CRC32()
			: this(DefaultPolynomial)
		{ }
		public CRC32(uint ulPolynomial)
		{
			this.Polynomial = ulPolynomial;
			this.HashSizeValue = 32;

			this.Initialize();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public static CRC32 Create()
		{
			return new CRC32();
		}

		/// <summary>
		/// 
		/// </summary>
		public static uint DefaultPolynomial
		{
			get { return 0xEDB88320; }
		}

		/// <summary>
		/// 
		/// </summary>
		public uint Polynomial { get; set; }

		private uint Value = 0xFFFFFFFF;

		private uint[] table;

		public override void Initialize()
		{
			BuildCRC32Table(this.Polynomial);
		}

		private void BuildCRC32Table(uint ulPolynomial)
		{
			uint dwCrc;
			table = new uint[256];

			for (uint i = 0; i < 256; i++)
			{
				dwCrc = i;

				for (int j = 8; j > 0; j--)
				{
					if ((dwCrc & 1) == 1)
						dwCrc = (dwCrc >> 1) ^ ulPolynomial;
					else
						dwCrc >>= 1;
				}

				table[i] = dwCrc;
			}
		}

		protected override void HashCore(byte[] array, int ibStart, int cbSize)
		{
			this.Value = 0xFFFFFFFF;

			for (int i = ibStart; i < cbSize; i++)
			{
				ulong tabPtr = (this.Value & 0xFF) ^ array[i];
				this.Value = (this.Value >> 8) ^ table[tabPtr];
			}
		}

		protected override byte[] HashFinal()
		{
			ulong finalCRC = this.Value ^ 0xffffffff;

			return new byte[] {
				(byte)((finalCRC >> 24) & 0xFF),
				(byte)((finalCRC >> 16) & 0xFF),
				(byte)((finalCRC >> 8) & 0xFF),
				(byte)((finalCRC >> 0) & 0xFF)
			};
		}
	}
}
